package com.simpou.commons.persistence.dao.impl;

import com.simpou.commons.model.entity.BaseEntity;
import com.simpou.commons.model.entity.IdentifiableEntity;
import com.simpou.commons.persistence.common.Cache;
import com.simpou.commons.persistence.common.Transaction;
import com.simpou.commons.persistence.dao.ObjectDAO;
import com.simpou.commons.utils.lang.CollectionsHelper;
import com.simpou.commons.utils.pagination.PageLimits;
import com.simpou.commons.utils.reflection.Casts;

import javax.persistence.EntityNotFoundException;
import java.io.Serializable;
import java.util.*;


/**
 * Implementação básica de um DAO que "persiste" os dados em memória. Os dados
 * existem somente em modo execução.
 *
 * @author Jonas Pereira
 * @version 2013-06-01
 * @since 2012-07-10
 */
public class InMemoryDAOImpl implements ObjectDAO {
    /**
     * Persistência em memória.
     */
    private final Map<Class<?>, List<?>> repositories = new HashMap<Class<?>, List<?>>();

    @Override
    public void flush() throws Exception {
    }

    @Override
    public <T extends IdentifiableEntity<?>> T getSingle(final Class<T> clasz,
                                                         final Serializable id) throws Exception {
        final List<T> list = getRepository(clasz);
        final T instance = newSingle(clasz, id);
        final int index = CollectionsHelper.search(list, instance);
        final T single;

        if (index < 0) {
            single = null;
        } else {
            single = Casts.simpleCast(list.get(index).doClone());
        }

        return single;
    }

    private <T extends IdentifiableEntity<?>> T newSingle(final Class<T> clasz,
                                                          final Serializable id) throws Exception {
        final T instance = clasz.newInstance();
        instance.uncheckedId(id);

        return instance;
    }

    @Override
    public <T extends IdentifiableEntity<?>> void delete(final Class<T> clasz,
                                                         final Serializable id) throws Exception {
        delete(newSingle(clasz, id));
    }

    @Override
    public <T extends BaseEntity> T create(final T entity) throws Exception {
        final T clone = Casts.simpleCast(entity.doClone());
        final Class<T> clasz = Casts.getClass(clone);
        final List<T> list = getRepository(clasz);
        list.add(clone);

        //TODO incluir gerador de id
        return entity;
    }

    @Override
    public <T extends BaseEntity> T update(final T entity) throws Exception {
        final T clone = Casts.simpleCast(entity.doClone());
        delete(clone);

        return create(clone);
    }

    @Override
    public void delete(final BaseEntity entity) throws Exception {
        final List<BaseEntity> list = Casts.simpleCast(getRepository(
                entity.getClass()));
        final int index = CollectionsHelper.search(list, entity);

        if (index < 0) {
            throw new EntityNotFoundException();
        } else {
            list.remove(index);
        }
    }

    @Override
    public <T extends BaseEntity> List<T> getList(final Class<T> clasz)
            throws Exception {
        return Collections.unmodifiableList(getRepository(clasz));
    }

    @Override
    public <T extends BaseEntity> Long count(final Class<T> clasz)
            throws Exception {
        final List<T> list = getRepository(clasz);

        return Long.valueOf(list.size());
    }

    @Override
    public <T extends BaseEntity> List<T> getList(final Class<T> clasz,
                                                  final PageLimits limits) throws Exception {
        final List<T> list = getRepository(clasz);

        return CollectionsHelper.subList(list, limits);
    }

    @Override
    public void refresh(final BaseEntity entity) throws Exception {

    }

    @Override
    public <T extends BaseEntity> void deleteAll(final Class<T> clasz)
            throws Exception {
        final List<T> repository = getRepository(clasz);
        repository.clear();
    }

    @Override
    public Transaction getTransaction() {
        return new DummyTransaction();
    }

    @Override
    public Cache getCache() {
        return new DummyCache();
    }

    @Override
    public Long count(final String stringQuery, final Object... params)
            throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public <T> List<T> getList(final Class<T> clasz,
                               final String stringQuery, final PageLimits limits, final Object... params)
            throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public <T> T getSingle(final Class<T> clasz,
                           final String stringQuery, final Object... params) throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int execute(final String stringQuery, final Object... params)
            throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public <T extends BaseEntity> List<T> getNamedList(final Class<T> clasz,
                                                       final String namedQuery, final PageLimits limits, final Object... params)
            throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public <T extends BaseEntity> T getNamedSingle(final Class<T> clasz,
                                                   final String namedQuery, final Object... params) throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int namedExecute(final String namedQuery, final Object... params)
            throws Exception {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    protected <T extends BaseEntity> boolean containsRepository(final Class<T> clasz) {
        return this.repositories.containsKey(clasz);
    }

    protected <T extends BaseEntity> List<T> getRepository(final Class<T> clasz) {
        final List<T> list;

        if (containsRepository(clasz)) {
            list = Casts.simpleCast(this.repositories.get(clasz));
        } else {
            list = new ArrayList<T>();
            this.repositories.put(clasz, list);
        }

        return list;
    }
}
